// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using Newtonsoft.Json;

namespace SnapObjects.Data
{
    public class PropertyAttributeInfo
    {
        public PropertyAttributeInfo(DynamicModeColumnAttributeInfo attributeInfo)
        {
            this.Column = attributeInfo;
        }

        public PropertyAttributeInfo(int index, PropertyInfo propertyInfo)
        {
            this.PropertySave = new PropertySaveAttribute(SaveStrategy.Save);

            var attributes = propertyInfo.GetCustomAttributes();

            if (attributes.Count() > 0)
            {
                this.InitAttributes(index, propertyInfo, attributes);

                //Special handling of guid type values because GetCustomAttributes cant available.
                if ((propertyInfo.PropertyType == typeof(Guid) ||
                     propertyInfo.PropertyType == typeof(Guid?)) &&
                    this.DefaultValueDefined)
                {
                    var defaultAttr = propertyInfo.GetCustomAttributesData()
                            .FirstOrDefault(m => m.AttributeType == typeof(DefaultValueAttribute));

                    var defaultData = defaultAttr.ConstructorArguments
                            .FirstOrDefault(m => m.Value.GetType() == typeof(string));

                    if (Guid.TryParse(defaultData.Value.ToString(), out var value))
                    {
                        this.DefaultValue = value;
                    }
                }
            }
        }

        public bool IsKey { get; protected set; }

        public bool IsIdentity { get; protected set; }

        public bool IsNotInWhere { get; private set; }

        public bool IsNotMapped { get; protected set; }

        public bool IsConcurrencyCheck { get; protected set; }

        public bool IsTimestamp { get; private set; }

        public bool IsCondition { get; private set; }

        public bool IsJsonIgnore { get; private set; }

        public bool IsXmlIgnore { get; private set; } //Not best practices, wait for optimization

        public bool ColumnDefined { get; private set; }

        public bool SqlColumnDefined { get; protected set; }

        public bool SqlComputerDefined { get; private set; }

        public bool DefaultValueDefined { get; private set; }

        public object DefaultValue { get; private set; }

        public IColumn Column { get; private set; }

        public ISqlColumn SqlColumn { get; protected set; }

        public ISqlCompute SqlComputer { get; private set; }

        public bool SqlJoinOnDefined { get; private set; }

        public ISqlEmbedded SqlEmbedded { get; private set; }

        public IModelEmbedded ModelEmbedded { get; private set; }

        public bool IsEmbedded { get; private set; }

        public IReadOnlyList<ISetValue> SetValueOperations { get; private set; }

        public bool HasSetValueOperation { get; private set; }

        public IPropertySave PropertySave { get; protected set; }

        public DatabaseGeneratedAttribute DatabaseGenerated { get; private set; }

        public bool DatabaseGeneratedDefined { get; private set; }

        public bool SqlDefaultValueDefined { get; private set; }

        public string SqlDefaultValue { get; private set; }

        public bool IsRequired { get; protected set; }

        public bool IsLimit { get; protected set; }

        public int Limit { get; protected set; }

        public bool HasConversion { get; private set; }

        internal ValueConverterInfo ValueConverterInfo { get; private set; }

        protected void InitAttributes(
            int index, PropertyInfo propertyInfo, IEnumerable<Attribute> attributes)
        {
            var setValueOperations = new List<ISetValue>();

            foreach (var attr in attributes)
            {
                if (this.TryParseAttribute(attr))
                {
                    continue;
                }

                if (attr is KeyAttribute)
                {
                    this.IsKey = true;
                }

                if (attr is IdentityAttribute)
                {
                    this.IsIdentity = true;
                }

                if (attr is NotInWhereAttribute)
                {
                    this.IsNotInWhere = true;
                }

                if (attr is NotMappedAttribute)
                {
                    this.IsNotMapped = true;
                }

                if (attr is ConcurrencyCheckAttribute)
                {
                    this.IsConcurrencyCheck = true;
                }

                if (attr is DefaultValueAttribute def)
                {
                    this.DefaultValueDefined = true;
                    this.DefaultValue = def.Value;
                }

                if (attr is TimestampAttribute)
                {
                    this.IsTimestamp = true;
                }

                if (attr is SqlColumnAttribute sqlColumn)
                {
                    this.SqlColumnDefined = true;
                    this.SqlColumn = sqlColumn;
                }
                else if (attr is SqlComputeAttribute sqlComputer)
                {
                    this.SqlComputerDefined = true;
                    this.SqlComputer = sqlComputer;
                }
                else if (attr is ColumnAttribute column)
                {
                    this.ColumnDefined = true;
                    this.Column = new ColumnAttributeInfo(column);
                }
                else if (attr is SqlEmbeddedAttribute sqlEmbedded)
                {
                    this.IsEmbedded = true;
                    this.SqlEmbedded = sqlEmbedded;
                }
                else if (attr is ModelEmbeddedAttribute modelEmbedded)
                {
                    this.IsEmbedded = true;
                    this.ModelEmbedded = modelEmbedded;
                }
                else if (attr is SetValueAttribute setValue)
                {
                    this.HasSetValueOperation = true;
                    setValueOperations.Add(setValue);
                }
                else if (attr is PropertySaveAttribute propertySave)
                {
                    this.PropertySave = propertySave;
                }
                else if (attr is JsonIgnoreAttribute range)
                {
                    this.IsJsonIgnore = true;
                }
                else if (attr is XmlIgnoreAttribute) //Not best practices, wait for optimization
                {
                    this.IsXmlIgnore = true;
                }
                else if (attr is DatabaseGeneratedAttribute databaseGenerated)
                {
                    this.DatabaseGenerated = databaseGenerated;
                    this.DatabaseGeneratedDefined = true;

                    if (databaseGenerated.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity)
                    {
                        this.IsIdentity = true;
                    }
                }
                else if (attr is SqlDefaultValueAttribute sqlDefaultValue)
                {
                    this.SqlDefaultValue = sqlDefaultValue.DefaultValue;
                    this.SqlDefaultValueDefined = true;
                }
                else if (attr is RequiredAttribute required)
                {
                    this.IsRequired = true;
                }
                else if (attr is StringLengthAttribute len)
                {
                    this.IsLimit = true;
                    this.Limit = len.MaximumLength;
                }
                else if (attr is MaxLengthAttribute max)
                {
                    this.IsLimit = true;
                    this.Limit = max.Length;
                }
                else if (attr is ValueConverterAttribute valueConverterAttribute)
                {
                    this.HasConversion = true;

                    this.ValueConverterInfo = 
                        new ValueConverterInfo(valueConverterAttribute, propertyInfo.PropertyType);
                }
            }

            this.SetValueOperations = setValueOperations;
        }

        protected virtual bool TryParseAttribute(Attribute attribute)
        {
            return false;
        }
    }
}
